#ifdef WX_PRECOMP //
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "sc/context.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include <boost/foreach.hpp>

#include <sstream>

#include <wx/string.h>
#include <wx/regex.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>

using namespace sc;


/**
* Clear the context.
*  Remove all information about it,
*  Remove setup.
*/
void Context::Clear(void)
{
    BOOST_FOREACH (SetupContext* pSetupCtx, m_setupContexts)
    {
        delete pSetupCtx;
    }
    m_setupContexts.clear();

    if (m_sccDoc)
    {
        xmlFreeDoc(m_sccDoc);
        m_sccDoc = NULL;
    }
}

/**
* Set the context file name
*
* @param sFileSCC  Context file name
*
* @return true if the RNG file exists and is valid
*/
bool Context::SetFile(const wxString& sFileSCC)
{
    m_sFilename = sFileSCC;
    return true;
}

/**
* Load a context
*
* @param sFilenameSCC SCC file name
*
* @return loading error
*/
int Context::Load(const wxString& sFilenameSCC)
{
	// Validate that the SCC file is valid
	wxString sFileSccGrammar = ::wxPathOnly(wxStandardPaths::Get().GetExecutablePath()) + wxT("/scc.rng");
	if (!sc::xml::RngValidate(sFilenameSCC, sFileSccGrammar))
		return error::SHEMA_INVALID;
	
    int error   = error::SHEMA_INVALID;
    m_sFilename = sFilenameSCC;
	
	wxLogMessage(wxT("Context::Load(%s), RNG=%s"), sFilenameSCC.c_str(), sFileSccGrammar.c_str());
	
    // Load the SCC document
    m_sccDoc = xmlReadFile(sFilenameSCC.utf8_str(), NULL, 0);
    if (m_sccDoc == NULL)
        return error::SHEMA_INVALID;

    xmlNodePtr root = xmlDocGetRootElement(m_sccDoc);

    wxLogMessage(wxT("Context::Load(%s)"), sFilenameSCC.c_str());

    if (root && !xmlStrcmp(root->name, xml::scc::_context))
    {
        error = error::NONE;

        // Load the title
        const xmlChar* sTitle = xmlGetProp(root, xml::scc::_title);
        if (sTitle)
        {
            m_sTitle = xml::ToWx(sTitle);
        }

        // Load the children
        xmlNodePtr child = root->children;
        while (child && !error)
        {
            if (child->type == XML_ELEMENT_NODE)
            {
                if (!xmlStrcmp(child->name, xml::scc::_file))
                {
                    SetupContext* pSetupCtx = new SetupContext(m_sFilename);
                    pSetupCtx->Load(child);
                    m_setupContexts.push_back(pSetupCtx);
                }
            }
            child = child->next;
        }
    }
    return error;
}

/**
* Save all setup file
*
* @return saving error
*/
int Context::SaveSetups(void) const
{
    int error = error::NONE;
    TSetupContexts::const_iterator iter;
    for (iter = m_setupContexts.begin(); iter != m_setupContexts.end() && !error; ++iter)
    {
        error = (*iter)->SaveSetup();
    }
    return error;
}


/**
* Clear the context.
*  Remove all information about it.
*/
void SetupContext::Clear(void)
{
    m_setup.Clear();
    m_MmiElements.clear();
    m_MmiAttributes.clear();
}

/**
* Load the context setup
*
* @param pNodeFile
*
* @return loading error
*/
int SetupContext::Load(const xmlNodePtr pNodeFile)
{
    int error = error::SHEMA_INVALID;

    const xmlChar* sXmlFile = xmlGetProp(pNodeFile, xml::scc::_setup);
    const xmlChar* sRngFile = xmlGetProp(pNodeFile, xml::scc::_rng);
    if (sXmlFile && sRngFile)
    {
        m_sFileXML = xml::ToWx(sXmlFile);
        if (SetConfigFile(xml::ToWx(sRngFile)))
        {
            wxString sPathRNG = MakePathAbsoluteToContext(m_sFileRNG);

            // Load Config
            error = m_setup.LoadConfig(sPathRNG);
            if (!error)
            {
                // Load Setup (xml file)
                wxString sPathXML = GetXmlPath();
                if (wxFileName::FileExists(sPathXML))
                {
                    error = m_setup.Load(sPathXML);
                }
            }
        }

        xmlNodePtr child = pNodeFile->children;
        while (child && !error)
        {
            if (child->type == XML_ELEMENT_NODE)
            {
                if (!xmlStrcmp(child->name, xml::scc::_xslt))
                {
                    error = LoadXSLT(child);
                }
                if (!xmlStrcmp(child->name, xml::scc::_mmi))
                {
                    error = LoadMMI(child);
                }
            }
            child = child->next;
        }
    }
    return error;
}

/**
* Load the XSL transformation
*
* @param pNodeXSLT
*
* @return loading error
*/
int SetupContext::LoadXSLT(const xmlNodePtr pNodeXSLT)
{
    int error = error::SHEMA_INVALID;

    const xmlChar* sXslFile     = xmlGetProp(pNodeXSLT, xml::scc::_xsl);
    const xmlChar* sXsltResFile = xmlGetProp(pNodeXSLT, xml::scc::_xsltres);
    if (sXslFile && sXsltResFile)
    {
        m_sFileXSL     = xml::ToWx(sXslFile);
        m_sFileXSLTRes = xml::ToWx(sXsltResFile);
        error = error::NONE;
    }
    return error;
}
/**
* Load the MMI context
*  - the way to print label
*  - icon for the tree item
*
* @param pNodeMMI
*
* @return loading error
*/
int SetupContext::LoadMMI(const xmlNodePtr pNodeMMI)
{
    int error = error::NONE;

    xmlNodePtr child = pNodeMMI->children;
    while (child && !error)
    {
        if (child->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(child->name, xml::scc::_element_label))
            {
                // Element label
                error = LoadElementLabel(child);
            }
            else if (!xmlStrcmp(child->name, xml::scc::_attribute_label))
            {
                // Attribute label
                error = LoadAttributeLabel(child);
            }
        }
        child = child->next;
    }
    return error;
}

/**
* Load the label of an Element
*
* @param pNodeLabel
*
* @return loading error
*/
int SetupContext::LoadElementLabel(const xmlNodePtr pNodeLabel)
{
    int error = error::SHEMA_INVALID;

    const xmlChar* sFor = xmlGetProp(pNodeLabel, xml::scc::_for);
    if (sFor != NULL)
    {
        error = error::NONE;

        wxString sElement = xml::ToWx(sFor);

        // Load the label value
        const xmlChar* sValue = xmlGetProp(pNodeLabel, xml::scc::_value);
        if (sValue)
        {
            m_MmiElements[sElement].sValue = xml::ToWx(sValue);
        }

        // load the default label value (if no value exist)
        const xmlChar* sDefault = xmlGetProp(pNodeLabel, xml::scc::_default);
        if (sDefault)
        {
            // Load the label value
            m_MmiElements[sElement].sDefault = xml::ToWx(sDefault);
        }

        // Icon
        const xmlChar* sIcon = xmlGetProp(pNodeLabel, xml::scc::_icon);
        if (sIcon)
        {
            // Load the icon path
            wxString sPathICO = MakePathAbsoluteToContext(xml::ToWx(sIcon));
            if (wxFileName::FileExists(sPathICO))
            {
                m_MmiElements[sElement].sIcon = sPathICO;
            }
        }
    }
    return error;
}

/**
* Load the label of an Attribute
*
* @param pNodeLabel
*
* @return loading error
*/
int SetupContext::LoadAttributeLabel(const xmlNodePtr pNodeLabel)
{
    int error = error::SHEMA_INVALID;

    const xmlChar* sXmlFor   = xmlGetProp(pNodeLabel, xml::scc::_for);
    const xmlChar* sXmlValue = xmlGetProp(pNodeLabel, xml::scc::_value);
    if (sXmlFor && sXmlValue)
    {
        wxString sFor = xml::ToWx(sXmlFor);
        m_MmiAttributes[sFor].sValue = xml::ToWx(sXmlValue);
        m_MmiAttributes[sFor].eType  = AttribType::STRING;

        const xmlChar* sXmlError = xmlGetProp(pNodeLabel, xml::scc::_error);
        if (sXmlError)
        {
            m_MmiAttributes[sFor].sError = xml::ToWx(sXmlError);
        }

        const xmlChar* sXmlHelp = xmlGetProp(pNodeLabel, xml::scc::_help);
        if (sXmlHelp)
        {
            wxString sHelp = xml::ToWx(sXmlHelp);
            sHelp.Replace(wxT("[br]"), wxT("\n"));
            m_MmiAttributes[sFor].sHelp = sHelp;
        }

        const xmlChar* sXmlType = xmlGetProp(pNodeLabel, xml::scc::_type);
        if (sXmlType)
        {
            if (xmlStrcmp(sXmlType, xml::scc::_type_directory) == 0)
            {
                m_MmiAttributes[sFor].eType = AttribType::DIRECTORY;
            }
            else if (xmlStrcmp(sXmlType, xml::scc::_type_file) == 0)
            {
                m_MmiAttributes[sFor].eType = AttribType::FILE;
            }
        }
        error = error::NONE;
    }
    return error;
}

/**
* Save the setup
*
* @return saving error
*/
int SetupContext::SaveSetup(void) const
{
    int error = error::CONTEXT_DONT_HAVE_SETUP;
    if (!m_sFileXML.empty())
    {
        error = m_setup.Save(GetXmlPath());

        if (!error)
            XslTransform();
    }
    return error;
}

/**
* Do the XSL transormation.
*
* @return XSL transformation error
*/
int SetupContext::XslTransform(void) const
{
    int error = error::XSL_CANT_TRANSFORM;

    wxLogMessage(wxT("SetupContext::XslTransform() - xsl=%s"), m_sFileXSL.c_str());

    if (!m_sFileXSL.empty())
    {
        wxString sPathXSL     = MakePathAbsoluteToContext(m_sFileXSL);
        wxString sPathXSLTRes = MakePathAbsoluteToContext(m_sFileXSLTRes);
        wxString sPathXML     = MakePathAbsoluteToContext(m_sFileXML);
        if (!xml::XslTransform(sPathXML, sPathXSL, sPathXSLTRes))
        {
            error = error::SHEMA_INVALID;
        }
    }
    return error;
}

/**
* Get the label of an Element according to the context
*
* @param rcElement element of which we need the label
*
* @return label
*/
wxString SetupContext::GetElementLabel(const setup::Element& rcElement) const
{
    wxString sLabel = rcElement.GetName();
    TMmiElements::const_iterator iter = m_MmiElements.find(sLabel);
    if (iter != m_MmiElements.end())
    {
        sLabel = iter->second.sValue;

        // Label is dynamic ?
        wxRegEx reDynLabel(wxT("%([a-zA-Z0-9-]*)%"));
        while (reDynLabel.Matches(sLabel))
        {
            size_t nbDynLabel = reDynLabel.GetMatchCount();
            for (size_t i = 1; i < nbDynLabel; ++i)
            {
                // Get 1 dynamic label
                wxString sTpl = reDynLabel.GetMatch(sLabel, i);
                setup::TAttributes::const_iterator iterAtt = std::find_if( rcElement.GetAttributes().begin()
                                                                         , rcElement.GetAttributes().end()
                                                                         , setup::PredFindAttributeByName(sTpl)
                                                                         );
                if (iterAtt != rcElement.GetAttributes().end())
                {
                    const setup::Attribute& rcAttribute = *iterAtt;
                    if (!rcAttribute.m_sValue.empty())
                    {
                        // Replace template by value
                        sLabel.Replace( wxString::Format(wxT("%%%s%%"), sTpl.c_str()), rcAttribute.m_sValue);
                    }
                    else
                    {
                        // Attribute empty : We remove all to use the default value
                        sLabel.Clear();
                        break;
                    }
                }
            }
        }

        // If label is empty, we fill it with the element name or default value
        if (sLabel.empty())
        {
            sLabel = iter->second.sDefault.empty() ? rcElement.GetName() : iter->second.sDefault;
        }
    }
    return sLabel;
}

/**
* Get the label of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the label
*
* @return label
*/
wxString SetupContext::GetAttributeLabel(const setup::Attribute& rcAttribute) const
{
    wxString sLabel = rcAttribute.GetName();
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(sLabel);
    if (iter != m_MmiAttributes.end())
    {
        sLabel = iter->second.sValue;
    }
    return sLabel;
}

/**
* Get the error text of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the error message
*
* @return label
*/
wxString SetupContext::GetAttributeError(const setup::Attribute& rcAttribute) const
{
    wxString sError;
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(rcAttribute.GetName());
    if (iter != m_MmiAttributes.end())
    {
        sError = iter->second.sError;
    }
    return sError;
}

/**
* Get the help text of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the help message
*
* @return label
*/
wxString SetupContext::GetAttributeHelp(const setup::Attribute& rcAttribute) const
{
    wxString sHelp;
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(rcAttribute.GetName());
    if (iter != m_MmiAttributes.end())
    {
        sHelp = iter->second.sHelp;
    }
    return sHelp;
}

/**
* Get the type text of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the type
*
* @return TypeCtx
*/
SetupContext::AttribType::Type SetupContext::GetAttributeType(const setup::Attribute& rcAttribute) const
{
    AttribType::Type eType = AttribType::STRING;
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(rcAttribute.GetName());
    if (iter != m_MmiAttributes.end())
    {
        eType = iter->second.eType;
    }
    return eType;
}

/**
* Set the config (Relax NG) file
*
* @param sFileRNG
*
* @return true if the RNG file exists and is valid
*/
bool SetupContext::SetConfigFile(const wxString& sFileRNG)
{
    bool bExist = false;
    wxString sPathRNG = MakePathAbsoluteToContext(sFileRNG);
    if (wxFileName::FileExists(sPathRNG))
    {
        if (Config::IsRngValid(sPathRNG))
        {
            m_sFileRNG = sFileRNG;
            bExist = true;
        }
    }
    return bExist;
}

/**
* Set the Setup (XML) file,
*
* @param sFileXML
* @param bRelative  set to true if you want a relative path to the context, or false if
*   you want an absolute path.
*
* @return true
*/
bool SetupContext::SetXmlFile(const wxString& sFileXML, bool bRelative)
{
    m_sFileXML = bRelative ? MakePathAbsoluteToContext(sFileXML) : sFileXML;
    return true;
}

/**
* Get the complete path/file os the XSL file
*
* @return XSL path
*/
wxString SetupContext::GetXslPath(void) const
{
    return MakePathAbsoluteToContext(m_sFileXSL);
}

/**
* Get the complete path/file of the XML file
*
* @return XML path
*/
wxString SetupContext::GetXmlPath(void) const
{
    return MakePathAbsoluteToContext(m_sFileXML);
}

/**
* Make the file name relative to the context (ie: SCC file)
*
* @param sFilename File name to make relative to the context
*
* @return File name relative to the context
*/
wxString SetupContext::MakePathAbsoluteToContext(const wxString& sFilename) const
{
    wxFileName fnSCC(*m_psFilename);
    wxFileName fnFile(fnSCC.GetPath() + wxT("/") + sFilename);
    fnFile.Normalize();
    return fnFile.GetFullPath();
}
